import numpy as np
from typing import Optional, Callable, Tuple, List

class TensorBase:
    def __init__(self):
        self._data: Optional[np.ndarray] = None
        self._grad: Optional[np.ndarray] = None
        self._creator: Optional[Callable] = None

    @property
    def dtype(self) -> np.dtype:
        return self._data.dtype

    @property
    def size(self) -> int:
        return self._data.size

    @property
    def shape(self) -> Tuple[int, ...]:
        return self._data.shape

    @property
    def nbytes(self) -> int:
        return self._data.nbytes

    def numpy(self) -> np.ndarray:
        return self._data

    def tolist(self) -> List:
        return self._data.tolist()

    def set_creator(self, func: Callable) -> None:
        self._creator = func

    def __str__(self) -> str:
        return f'Tensor <{self.dtype}, {self.shape}>\n{self._data}'
